﻿//-----------------------------------------------------------------------
// <copyright file="AgsMapResource.cs" company="WorldView Solutions Inc">
//     Copyright (c) WorldView Solutions Inc. This source is subject to the Microsoft Public License (Ms-PL). All other rights reserved.
// </copyright>
//-----------------------------------------------------------------------

using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Web;
using System.Data;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using ESRI.ArcGIS.Server;
using ESRI.ArcGIS.ADF.ArcGISServer;

namespace WorldViewSolutions.ArcGISServer.SOE.DataSources
{

    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
    public class AgsMapResource : IMapDResource
    {
        #region IMapDResource Members

        /// <summary>
        /// Gets a value indicating whether this instance is cached.
        /// </summary>
        /// <remarks></remarks>
        public bool IsCached
        {
            get { return false; }
        }

        /// <summary>
        /// Gets the tile cache info.
        /// </summary>
        /// <remarks></remarks>
        public TileCacheInfo TileCacheInfo
        {
            get { return null; }
        }


        /// <summary>
        /// Prior to calling this method you must setup the MapDescription and ImageDescription properties
        /// </summary>
        /// <param name="widthInches">The width inches.</param>
        /// <param name="heightInches">The height inches.</param>
        /// <param name="outputDPI">The output DPI.</param>
        /// <param name="maxResourceDpi">The max resource dpi.</param>
        /// <param name="extentToDraw">The extent to draw.</param>
        /// <returns></returns>
        /// <remarks></remarks>
		public Bitmap DrawExtent(double widthInches, double heightInches, int outputDPI, int maxResourceDpi, EnvelopeN extentToDraw)
        {
            int MaxServiceWidth = 2048;
            int MaxServiceHeight = 2048;
            int dpi = 96;
            int outputWidthPixels = Convert.ToInt32(Math.Round(((double)widthInches * dpi), 0));
            int outputHeightPixels = Convert.ToInt32(Math.Round(((double)heightInches * dpi), 0));
                       
            this.ImageDescription.ImageDisplay.ImageWidth = outputWidthPixels;
			this.ImageDescription.ImageDisplay.ImageHeight = outputHeightPixels;
			this.ImageDescription.ImageDisplay.ImageDPI = outputDPI;

            if (this.ImageDescription.ImageDisplay.TransparentColor == null)
            {
                this.ImageDescription.ImageDisplay.TransparentColor = _backgroundColor;
            }

            this.ImageDescription.ImageType.ImageFormat = esriImageFormat.esriImagePNG24;
            this.ImageDescription.ImageType.ImageReturnType = esriImageReturnType.esriImageReturnMimeData;


            this.MapDescription.MapArea.Extent = extentToDraw;

            System.Drawing.Bitmap image = null;
            using (MapServerProxy proxy = new MapServerProxy(SoapUrl))
            {
                PropertySet serviceproperties = proxy.GetServiceConfigurationInfo();
                PropertySetProperty[] propertyarray = serviceproperties.PropertyArray;
                foreach (PropertySetProperty serviceprop in propertyarray)
                {
                    string key = serviceprop.Key.ToString();
                    string value = serviceprop.Value.ToString();

                    if (key == "MaximumImageWidth")
                        MaxServiceWidth = Convert.ToInt16(value);

                    if (key == "MaximumImageHeight")
                        MaxServiceHeight = Convert.ToInt16(value);
                }

                double resampleRatio = 0;
                if (outputWidthPixels > MaxServiceWidth)
                {
                    resampleRatio = (double)MaxServiceWidth / (double)outputWidthPixels;
                 }

                if (outputHeightPixels > MaxServiceHeight)
                {
                    double resampleHeightRatio = (double)MaxServiceHeight / (double)outputHeightPixels;
                    
                    if (resampleHeightRatio > resampleRatio)
                        resampleRatio = resampleHeightRatio;
                }

               
                if (resampleRatio > 0)
                {
                    this.ImageDescription.ImageDisplay.ImageWidth = Convert.ToInt16(Math.Floor(this.ImageDescription.ImageDisplay.ImageWidth * resampleRatio));
					this.ImageDescription.ImageDisplay.ImageHeight = Convert.ToInt16(Math.Floor(this.ImageDescription.ImageDisplay.ImageHeight * resampleRatio));
                    this.ImageDescription.ImageDisplay.ImageDPI = Convert.ToInt16(Math.Round(this.ImageDescription.ImageDisplay.ImageDPI * resampleRatio));
                }

				this.MapDescription.SpatialReference = extentToDraw.SpatialReference;

				ESRI.ArcGIS.ADF.ArcGISServer.MapImage mapImage = proxy.ExportMapImage(this.MapDescription, this.ImageDescription);								
                this.MapDescription.SpatialReference = this.SpatialReference;

                // create a copy of the image to prevent stream corruption
                using (System.IO.MemoryStream ms = new System.IO.MemoryStream(mapImage.ImageData))
                {
                    image = new System.Drawing.Bitmap(ms);
                    image = new Bitmap(Image.FromStream(ms));
                }
            }

            if (outputWidthPixels != this.ImageDescription.ImageDisplay.ImageWidth || outputHeightPixels != this.ImageDescription.ImageDisplay.ImageHeight)
            {
				System.Drawing.Color antiAliasAutoColor = System.Drawing.Color.FromArgb(140, 140, 140);

                System.Drawing.Bitmap resampledImage = new Bitmap(outputWidthPixels, outputHeightPixels);
				System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(resampledImage);
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

                g.FillRectangle(new System.Drawing.SolidBrush(antiAliasAutoColor), 0, 0, outputWidthPixels, outputHeightPixels);
                g.DrawImage(image, 0, 0, outputWidthPixels, outputHeightPixels);
                g.Dispose();
                image = resampledImage;

                try
                {
                    image.MakeTransparent(antiAliasAutoColor);
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
                        
            return image;
        }

		 #endregion

        #region [ Local Variables ]

        /// <summary>
        /// 
        /// </summary>
		private ESRI.ArcGIS.ADF.ArcGISServer.Color _backgroundColor = null;
        /// <summary>
        /// 
        /// </summary>
        private int[] _queryableLayerIDs = null;
        /// <summary>
        /// 
        /// </summary>
        private string[] _queryableLayerNames = null;
        /// <summary>
        /// 
        /// </summary>
        private SpatialReference _spatialReference = null;
        /// <summary>
        /// 
        /// </summary>
        private EnvelopeN _fullExtent = null;
        /// <summary>
        /// 
        /// </summary>
        private EnvelopeN _defaultExtent = null;
        /// <summary>
        /// 
        /// </summary>
        private string _serviceInfoAsJSON = null;
        /// <summary>
        /// 
        /// </summary>
        private MapDescription _mapDescription = null;
        /// <summary>
        /// 
        /// </summary>
		private MapServerLegendInfo[] _mapServerLegendInfos = null;
        /// <summary>
        /// 
        /// </summary>
		private ImageDescription _imageDescription = null;
        /// <summary>
        /// 
        /// </summary>
        private esriUnits _units = esriUnits.esriUnknownUnits;
        /// <summary>
        /// 
        /// </summary>
		private MapLayerInfo[] _mapLayerInfos = null;
        #endregion

        #region [ Properties ]

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        /// <remarks></remarks>
        public string Name { get; set; }
        /// <summary>
        /// Gets or sets the host.
        /// </summary>
        /// <value>The host.</value>
        /// <remarks></remarks>
        public string Host { get; set; }
        /// <summary>
        /// Gets or sets the token.
        /// </summary>
        /// <value>The token.</value>
        /// <remarks></remarks>
		public string Token { get; set; }
        /// <summary>
        /// Gets or sets the service.
        /// </summary>
        /// <value>The service.</value>
        /// <remarks></remarks>
        public string Service { get; set; }
        /// <summary>
        /// Gets or sets the base URL.
        /// </summary>
        /// <value>The base URL.</value>
        /// <remarks></remarks>
        public string BaseUrl { get; set; }
        /// <summary>
        /// Gets or sets the SOAP URL.
        /// </summary>
        /// <value>The SOAP URL.</value>
        /// <remarks></remarks>
        public string SoapUrl { get; set; }
        /// <summary>
        /// Gets or sets the rest URL.
        /// </summary>
        /// <value>The rest URL.</value>
        /// <remarks></remarks>
        public string RestUrl { get; set; }
        /// <summary>
        /// Gets or sets the cache directory.
        /// </summary>
        /// <value>The cache directory.</value>
        /// <remarks></remarks>
        public string CacheDirectory { get; set; }
        /// <summary>
        /// Gets or sets the cache URL.
        /// </summary>
        /// <value>The cache URL.</value>
        /// <remarks></remarks>
        public string CacheURL { get; set; }

        /// <summary>
        /// Gets the image description.
        /// </summary>
        /// <remarks></remarks>
        public ImageDescription ImageDescription
        {
            get
            {
                if (_imageDescription == null)
                {
                    _imageDescription = new ImageDescription();
                    _imageDescription.ImageDisplay = new ImageDisplay();
                    _imageDescription.ImageDisplay.ImageDPI = 96;
                    _imageDescription.ImageDisplay.ImageWidth = 512;
                    _imageDescription.ImageDisplay.ImageHeight = 512;
                    _imageDescription.ImageType = new ImageType();
                    _imageDescription.ImageType.ImageFormat = esriImageFormat.esriImagePNG24;
                    _imageDescription.ImageType.ImageReturnType = esriImageReturnType.esriImageReturnMimeData;
                }
                return _imageDescription;
            }
        }

        /// <summary>
        /// Gets the map server legend infos.
        /// </summary>
        /// <remarks></remarks>
		public MapServerLegendInfo[] MapServerLegendInfos
        {
            get
            {
				if (_mapServerLegendInfos == null)
                {
					GetServiceInfo();					
                }
				return _mapServerLegendInfos;
            }
        }

        /// <summary>
        /// Gets the units.
        /// </summary>
        /// <remarks></remarks>
        public esriUnits Units
        {
            get
            {
				if (_units == esriUnits.esriUnknownUnits)
                {
                    GetServiceInfo();
                }
                return _units;
            }
        }

        /// <summary>
        /// Gets the map description.
        /// </summary>
        /// <remarks></remarks>
        public MapDescription MapDescription
        {
            get
            {
                if (_mapDescription == null)
                {
                    GetServiceInfo();
                }
                return _mapDescription;
            }
        }
        /// <summary>
        /// Gets the spatial reference.
        /// </summary>
        /// <remarks></remarks>
        public SpatialReference SpatialReference
        {
            get
            {
                if (_spatialReference == null)
                {
                    GetServiceInfo();
                }
                return _spatialReference;
            }
        }
        /// <summary>
        /// Gets the full extent.
        /// </summary>
        /// <remarks></remarks>
        public EnvelopeN FullExtent
        {
            get
            {
                if (_fullExtent == null)
                {
                    GetServiceInfo();
                }
                return _fullExtent;
            }
        }
        /// <summary>
        /// Gets the default extent.
        /// </summary>
        /// <remarks></remarks>
        public EnvelopeN DefaultExtent
        {
            get
            {
                if (_defaultExtent == null)
                {
                    GetServiceInfo();
                }
                return _defaultExtent;
            }
        }
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="AgsMapResource"/> class.
        /// </summary>
        /// <param name="soapUrl">The SOAP URL.</param>
        /// <remarks></remarks>
        public AgsMapResource(string soapUrl)
        {
            if (soapUrl.EndsWith("/"))
            {
                // soap url's break if there is trailing slash before the querystring ?
                soapUrl = soapUrl.Remove(soapUrl.Length - 1);
            }

            this.SoapUrl = soapUrl;
        }

        /// <summary>
        /// Computes the scale.
        /// </summary>
        /// <param name="widthInches">The width inches.</param>
        /// <param name="heightInches">The height inches.</param>
        /// <param name="outputDPI">The output DPI.</param>
        /// <param name="extentToDraw">The extent to draw.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public double ComputeScale(double widthInches, double heightInches, int outputDPI, EnvelopeN extentToDraw)
        {
            int outputWidthPixels = Convert.ToInt32(Math.Round(((double)widthInches * outputDPI)));
            int outputHeightPixels = Convert.ToInt32(Math.Round(((double)heightInches * outputDPI)));

            this.ImageDescription.ImageDisplay.ImageWidth = outputWidthPixels;
            this.ImageDescription.ImageDisplay.ImageHeight = outputHeightPixels;
            this.ImageDescription.ImageDisplay.ImageDPI = outputDPI;

            this.MapDescription.MapArea.Extent = extentToDraw;
            this.MapDescription.SpatialReference = extentToDraw.SpatialReference;

            using (MapServerProxy proxy = new MapServerProxy(SoapUrl))
            {
                return proxy.ComputeScale(this.MapDescription, this.ImageDescription.ImageDisplay);
            }
        }

        /// <summary>
        /// Draws the extents to PDF.
        /// </summary>
        /// <param name="widthInches">The width inches.</param>
        /// <param name="heightInches">The height inches.</param>
        /// <param name="extentToDraw">The extent to draw.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public List<ExportedLayer> DrawExtentsToPDF(double widthInches, double heightInches, EnvelopeN extentToDraw)
        {
            List<ExportedLayer> images = new List<ExportedLayer>();

            // get a list of all visible layes
            List<int> visibleLayers = new List<int>();

            // Turn all layers off so we can export each one individually
            foreach (MapLayerInfo li in _mapLayerInfos)
            {
                if (!li.IsComposite && IsLayerVisible(li))
                {
                    visibleLayers.Add(li.LayerID);
                    _mapDescription.LayerDescriptions.First(l => l.LayerID == li.LayerID).Visible = false;
                }
            }

            // export each layer
            foreach (int id in visibleLayers)
            {
                LayerDescription ld = this.MapDescription.LayerDescriptions.First(l => l.LayerID == id);
                MapLayerInfo li = _mapLayerInfos.First(l => l.LayerID == id);
                ld.Visible = true;
                byte[] imageData = DrawExtentToPDF(widthInches, heightInches, extentToDraw);
                if (imageData != null)
                {
                    images.Add(new ExportedLayer(li.Name, imageData));
                }

                ld.Visible = false;
            }

            // reset the visibilties back to their original state
            foreach (int id in visibleLayers)
            {
                foreach (LayerDescription ld in this.MapDescription.LayerDescriptions)
                {
                    if (ld.LayerID == id)
                    {
                        ld.Visible = true;
                    }
                }
            }

            return images;
        }

        /// <summary>
        /// Determines whether [is layer visible] [the specified layer info].
        /// </summary>
        /// <param name="layerInfo">The layer info.</param>
        /// <returns><c>true</c> if [is layer visible] [the specified layer info]; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        private bool IsLayerVisible(MapLayerInfo layerInfo)
        {
            LayerDescription layerDesc = _mapDescription.LayerDescriptions.First(l => l.LayerID == layerInfo.LayerID);
            if (!layerDesc.Visible) return false;

            // check its parent(s) layer
            if (layerInfo.ParentLayerID > -1)
            {
                return IsLayerVisible(_mapLayerInfos.First(l => l.LayerID == layerInfo.ParentLayerID));
            }
            return true;
        }

        /// <summary>
        /// Draws the extent to PDF.
        /// </summary>
        /// <param name="widthInches">The width inches.</param>
        /// <param name="heightInches">The height inches.</param>
        /// <param name="extentToDraw">The extent to draw.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public byte[] DrawExtentToPDF(double widthInches, double heightInches, EnvelopeN extentToDraw)
        {
            int MaxServiceWidth = 2048;
            int MaxServiceHeight = 2048;
            int dpi = 96;
            int outputWidthPixels = Convert.ToInt32(Math.Round(((double)widthInches * dpi), 0));
            int outputHeightPixels = Convert.ToInt32(Math.Round(((double)heightInches * dpi), 0));

            this.MapDescription.BackgroundSymbol = null;
            this.ImageDescription.ImageDisplay.TransparentColor = null;
            this.ImageDescription.ImageType.ImageFormat = esriImageFormat.esriImagePDF;
            this.ImageDescription.ImageType.ImageReturnType = esriImageReturnType.esriImageReturnMimeData;

            this.ImageDescription.ImageDisplay.ImageWidth = outputWidthPixels;
            this.ImageDescription.ImageDisplay.ImageHeight = outputHeightPixels;
            this.ImageDescription.ImageDisplay.ImageDPI = dpi;


            this.MapDescription.MapArea.Extent = extentToDraw;
            this.MapDescription.SpatialReference = extentToDraw.SpatialReference;



            using (MapServerProxy proxy = new MapServerProxy(SoapUrl))
            {
                PropertySet serviceproperties = proxy.GetServiceConfigurationInfo();
                PropertySetProperty[] propertyarray = serviceproperties.PropertyArray;
                foreach (PropertySetProperty serviceprop in propertyarray)
                {
                    string key = serviceprop.Key.ToString();
                    string value = serviceprop.Value.ToString();

                    if (key == "MaximumImageWidth")
                        MaxServiceWidth = Convert.ToInt16(value);

                    if (key == "MaximumImageHeight")
                        MaxServiceHeight = Convert.ToInt16(value);
                }

                ESRI.ArcGIS.ADF.ArcGISServer.MapImage mapImage = proxy.ExportMapImage(this.MapDescription, this.ImageDescription);
                return mapImage.ImageData;
            }
        }


        /// <summary>
        /// Gets the service info.
        /// </summary>
        /// <remarks></remarks>
        private void GetServiceInfo()
        {
            using (MapServerProxy proxy = new MapServerProxy(SoapUrl))
            {
                string defaultMapName = proxy.GetDefaultMapName();
                MapServerInfo msi = proxy.GetServerInfo(defaultMapName);
				
                _units = msi.Units;
                _spatialReference = msi.SpatialReference;

                _fullExtent = msi.FullExtent as EnvelopeN;
                _defaultExtent = msi.Extent as EnvelopeN;
                _mapDescription = msi.DefaultMapDescription;
				_backgroundColor = msi.BackgroundColor;
				_mapLayerInfos = msi.MapLayerInfos;
				_mapServerLegendInfos = GetLegendInfos(msi, proxy);

            }			
        }

        /// <summary>
        /// Gets the legend infos.
        /// </summary>
        /// <param name="msi">The msi.</param>
        /// <param name="proxy">The proxy.</param>
        /// <returns></returns>
        /// <remarks></remarks>
		private MapServerLegendInfo[] GetLegendInfos(MapServerInfo msi, MapServerProxy proxy)
		{
			ImageType imgtype = new ImageType();
			imgtype.ImageFormat = esriImageFormat.esriImagePNG;
			imgtype.ImageReturnType = esriImageReturnType.esriImageReturnMimeData;

			MapServerLegendPatch legendpatch = new MapServerLegendPatch();

			legendpatch.ImageDPI = 96;
			legendpatch.Height = 10;
			legendpatch.Width = 10;

			return proxy.GetLegendInfo(msi.DefaultMapDescription.Name, null, legendpatch, imgtype);
		}
    }
}